package com.terrier.net
{
    import com.terrier.net.events.WxSocketEvent;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.Socket;
    import flash.utils.ByteArray;

    /**
     * 接收到完整数据时调度
     * @eventType com.terrier.net.events.WxSocketEvent.SOCKET_DATA
     */
    [Event(name="socketData", type="com.terrier.net.events.WxSocketEvent")]

    /**
     * 在建立网络连接后调度。
     * @eventType flash.events.Event.CONNECT
     */
    [Event(name="connect", type="flash.events.Event")]

    /**
     * 在服务器关闭套接字连接时调度。 仅在服务器关闭连接时调度 close 事件；在调用 close() 方法时不调度该事件。
     * @eventType flash.events.Event.CLOSE
     */
    [Event(name="close", type="flash.events.Event")]

    /**
     * 在出现输入/输出错误并导致发送或加载操作失败时调度。
     * @eventType flash.events.IOErrorEvent.IO_ERROR
     */
    [Event(name="ioError", type="flash.events.IOErrorEvent")]

    /**
     * 若对 connect() 的调用尝试连接到调用方安全沙箱外部的服务器或端口号低于 1024 端口，则进行调度。
     * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
     */
    [Event(name="securityError", type="flash.events.SecurityErrorEvent")]

    /**
     * WxSocket
     * @author Jarvis.weng
     */
    public class WxSocket extends EventDispatcher
    {
        private var _socket:Socket;
        private var _strategy:IWxSocketStrategy;

        /**
         * 构造函数,创建一个新的WxSocket对象.
         * @param strategy 读写策略对象. WxSocket使用该策略对象来决定具体的读写数据的逻辑
         */
        public function WxSocket(strategy:IWxSocketStrategy = null)
        {
            super();

            _socket = new Socket();
            _socket.addEventListener(Event.CONNECT, connectHandler);
            _socket.addEventListener(Event.CLOSE, closeHandler);
            _socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
            _socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            _socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);

            _strategy = (strategy == null) ? new WxSocketDefaultStrategy() : strategy;
            _strategy.initSocket(_socket);
        }

        //--------------------------------------------------------
        //
        // Public Functions
        //
        //--------------------------------------------------------

        /**
         * 将套接字连接到指定的主机和端口。
         * 如果连接立即失败，则调度一个事件或引发一个异常。如果指定了主机，则调度一个错误事件；如果未指定主机，则引发异常。
         * 另外，连接的状态由事件来报告。 如果已连接上套接字，则现有的连接将首先关闭。
         * @param host 要连接到的主机的名称或 IP 地址。如果未指定主机，则要连接的主机为执行调用的 文件所在的主机。
         * @param port 要连接的端口号
         */
        public function connect(host:String, port:int):void
        {
            _socket.connect(host, port);
        }

        /**
         * 关闭套接字。 在调用 close() 方法之后，将无法读取或写入任何数据。
         * 仅在服务器关闭连接时调度 close 事件；在调用 close() 方法时不调度该事件。
         * 可通过再次对 Socket 对象调用 connect() 方法来重复使用该对象。
         */
        public function close():void
        {
            _socket.close();
        }

        /**
         * 发送数据
         * @param data 数据
         */
        public function send(data:*):void
        {
            if (_socket.connected)
            {
                if (_strategy.writeData(_socket, data))
                {
                    _socket.flush();
                }
            }
        }

        //--------------------------------------------------------
        //
        // Public Properties
        //
        //--------------------------------------------------------

        /**
         * 指示此对象目前是否已连接。 如果该套接字当前已连接，则对此属性的调用将返回值 true，否则将返回 false。
         * @return
         */
        public function get connected():Boolean
        {
            return _socket.connected;
        }

        /**
         * 指示建立连接时需等待的毫秒数。 如果在指定时间内未成功建立连接，则连接失败。默认值是 20,000（20 秒）。
         * @return
         */
        public function get timeout():uint
        {
            return _socket.timeout;
        }

        /**
         * @private
         */
        public function set timeout(value:uint):void
        {
            _socket.timeout = value;
        }

        //--------------------------------------------------------
        //
        // Private Functions
        //
        //--------------------------------------------------------

        private function socketDataHandler(e:ProgressEvent):void
        {
            do
            {
                var data:ByteArray = _strategy.readData(_socket);
                if (data)
                {
                    var event:WxSocketEvent = new WxSocketEvent(WxSocketEvent.SOCKET_DATA, data);
                    dispatchEvent(event);
                }
            } while (_strategy.readMore);
        }

        private function closeHandler(e:Event):void
        {
            dispatchEvent(e);
        }

        private function connectHandler(e:Event):void
        {
            dispatchEvent(e);
        }

        private function ioErrorHandler(e:IOErrorEvent):void
        {
            dispatchEvent(e);
        }

        private function securityErrorHandler(e:SecurityErrorEvent):void
        {
            dispatchEvent(e);
        }
    }
}